/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.viper;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
//import java.util.Random;

import com.example.android.viper.R;

import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.View;
import android.widget.TextView;

/**
 * ViperView: implementation of a simple game of Snake
 * 
 * 
 */
public class ViperView extends TileView
{

	private static final String TAG = "ViperView";

	/**
	 * Current mode of application: READY to run, RUNNING, or you have already
	 * lost. static final ints are used instead of an enum for performance
	 * reasons.
	 */
	// Global features
	private static int currentLevel = 1;
	private static boolean dupCommand = false;
	private static boolean flagQueue = false;
	private static boolean vibration = true;
	private static boolean key2interacton = true;
	
	private static int MAX_LEVEL = 10;	
	private static int EGG_VIB_DURATION = 0;
	private static int PRESSKEY_VIB_DURATION = 40;
	private static int SELFBITE_VIB_DURATION = 600;
	private static int WIN_VIB_DURATION = 600;
	private static int GAME_DELAY = 120; //150;
	
	// States
	private int mMode = READY;
	public static final int PAUSE = 0;
	public static final int READY = 1;
	public static final int RUNNING = 2;
	public static final int LOSE = 3;
	public static final int WIN = 4;

	/**
	 * Current direction the snake is headed.
	 */
	private int mDirection = SOUTH;
	private static final int NORTH = 1;
	private static final int SOUTH = 2;
	private static final int EAST = 3;
	private static final int WEST = 4;

	/**
	 * Labels for the drawables that will be loaded into the TileView class
	 */
	private static final int WALL_TILE = 0;
	private static final int EGG = 17;
	private static final int SNAKE_TILE = 18;

	private static final int totTiles = 36;

	/**
	 * mScore: used to track the number of apples captured mMoveDelay: number of
	 * milliseconds between snake movements. This will decrease as apples are
	 * captured.
	 */
	private long mScore = 0;
	private long mMoveDelay = GAME_DELAY;
	private Vibrator vib = (Vibrator) getContext().getSystemService(
			Context.VIBRATOR_SERVICE);

	/**
	 * mLastMove: tracks the absolute time when the snake last moved, and is
	 * used to determine if a move should be made based on mMoveDelay.
	 */
	private long mLastMove;

	/**
	 * mStatusText: text shows to the user in some run states
	 */
	private TextView mStatusText;

	/**
	 * mSnakeTrail: a list of Coordinates that make up the snake's body
	 * mAppleList: the secret location of the juicy apples the snake craves.
	 */
	private byte[] levelBuffer;
	private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();
	private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();
	private ArrayList<Integer> mCommandList = new ArrayList<Integer>();

	/**
	 * Everyone needs a little randomness in their life
	 */
	// private static final Random RNG = new Random();

	/**
	 * Create a simple handler that we can use to cause animation to happen. We
	 * set ourselves as a target and we can use the sleep() function to cause an
	 * update/invalidate to occur at a later date.
	 */
	private RefreshHandler mRedrawHandler = new RefreshHandler();

	class RefreshHandler extends Handler
	{

		@Override
		public void handleMessage(Message msg)
		{
			ViperView.this.update();
			ViperView.this.invalidate();
		}

		public void sleep(long delayMillis)
		{
			this.removeMessages(0);
			sendMessageDelayed(obtainMessage(0), delayMillis);
		}
	};

	/**
	 * Constructs a SnakeView based on inflation from XML
	 * 
	 * @param context
	 * @param attrs
	 */
	public ViperView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		initViperView();
	}

	public ViperView(Context context, AttributeSet attrs, int defStyle)
	{
		super(context, attrs, defStyle);
		initViperView();
	}

	private void initViperView()
	{
		Log.d("ViperView", "init viper view");
		setFocusable(true);

		Resources r = this.getContext().getResources();

		currentLevel = Config.State().currentLevel;
		
		if (levelBuffer == null)
		{
			levelBuffer = new byte[723];
		}

		resetTiles(totTiles);
		loadTile(WALL_TILE + 0, r.getDrawable(R.drawable.wall00));
		loadTile(WALL_TILE + 1, r.getDrawable(R.drawable.wall01));
		loadTile(WALL_TILE + 2, r.getDrawable(R.drawable.wall02));
		loadTile(WALL_TILE + 3, r.getDrawable(R.drawable.wall03));
		loadTile(WALL_TILE + 4, r.getDrawable(R.drawable.wall04));
		loadTile(WALL_TILE + 5, r.getDrawable(R.drawable.wall05));
		loadTile(WALL_TILE + 6, r.getDrawable(R.drawable.wall06));
		loadTile(WALL_TILE + 7, r.getDrawable(R.drawable.wall07));
		loadTile(WALL_TILE + 8, r.getDrawable(R.drawable.wall08));
		loadTile(WALL_TILE + 9, r.getDrawable(R.drawable.wall09));
		loadTile(WALL_TILE + 10, r.getDrawable(R.drawable.wall10));
		loadTile(WALL_TILE + 11, r.getDrawable(R.drawable.wall11));
		loadTile(WALL_TILE + 12, r.getDrawable(R.drawable.wall12));
		loadTile(WALL_TILE + 13, r.getDrawable(R.drawable.wall13));
		loadTile(WALL_TILE + 14, r.getDrawable(R.drawable.wall14));
		loadTile(WALL_TILE + 15, r.getDrawable(R.drawable.wall15));
		loadTile(WALL_TILE + 16, r.getDrawable(R.drawable.wall16));

		loadTile(EGG, r.getDrawable(R.drawable.egg));

		loadTile(SNAKE_TILE + 0, r.getDrawable(R.drawable.snake00));
		loadTile(SNAKE_TILE + 1, r.getDrawable(R.drawable.snake01));
		loadTile(SNAKE_TILE + 2, r.getDrawable(R.drawable.snake02));
		loadTile(SNAKE_TILE + 3, r.getDrawable(R.drawable.snake03));
		loadTile(SNAKE_TILE + 4, r.getDrawable(R.drawable.snake04));
		loadTile(SNAKE_TILE + 5, r.getDrawable(R.drawable.snake05));
		loadTile(SNAKE_TILE + 6, r.getDrawable(R.drawable.snake06));
		loadTile(SNAKE_TILE + 7, r.getDrawable(R.drawable.snake07));
		loadTile(SNAKE_TILE + 8, r.getDrawable(R.drawable.snake08));
		loadTile(SNAKE_TILE + 9, r.getDrawable(R.drawable.snake09));
		loadTile(SNAKE_TILE + 10, r.getDrawable(R.drawable.snake10));
		loadTile(SNAKE_TILE + 11, r.getDrawable(R.drawable.snake11));
		loadTile(SNAKE_TILE + 12, r.getDrawable(R.drawable.snake12));
		loadTile(SNAKE_TILE + 13, r.getDrawable(R.drawable.snake13));
		loadTile(SNAKE_TILE + 14, r.getDrawable(R.drawable.snake14));
		loadTile(SNAKE_TILE + 15, r.getDrawable(R.drawable.snake15));
		loadTile(SNAKE_TILE + 16, r.getDrawable(R.drawable.snake16));
		loadTile(SNAKE_TILE + 17, r.getDrawable(R.drawable.snake17));
	}

	private void initNewGame()
	{
		// Load level
		InputStream fIn = null;

		fIn = getResources().openRawResource(R.raw.level01 + (currentLevel - 1));
		try
		{
			fIn.read(levelBuffer);
			fIn.close();
		} catch (IOException e)
		{
			e.printStackTrace();
		}
		
		setColorTiles((int)(10+(float)(currentLevel)/MAX_LEVEL*340), WALL_TILE+1, EGG-1);

		mSnakeTrail.clear();
		mAppleList.clear();
		mCommandList.clear();

		// For now we're just going to load up a short default eastbound snake
		// that's just turned north

		mSnakeTrail.add(new Coordinate(9, 17));
		mSnakeTrail.add(new Coordinate(9, 16));
		mSnakeTrail.add(new Coordinate(9, 15));
		mDirection = SOUTH;

		// Setup apples to start with
		for (int x = 0; x < mXTileCount; x++)
		{
			for (int y = 0; y < mYTileCount; y++)
			{
				if (WALL_TILE + levelBuffer[(y + x * mXTileCount) * 2] == EGG)
				{
					addApple(x, y);
					//x=100;
					//y=100;
					//break;
				}
			}
		}
		// addRandomApple();
		// addRandomApple();

		mMoveDelay = GAME_DELAY;
		mScore = 0;

		// Create level
		clearTiles();
		updateWalls();
		updateApples();
	}
	

	/**
	 * Given a ArrayList of coordinates, we need to flatten them into an array
	 * of ints before we can stuff them into a map for flattening and storage.
	 * 
	 * @param cvec
	 *            : a ArrayList of Coordinate objects
	 * @return : a simple array containing the x/y values of the coordinates as
	 *         [x1,y1,x2,y2,x3,y3...]
	 */
	private int[] coordArrayListToArray(ArrayList<Coordinate> cvec)
	{
		int count = cvec.size();
		int[] rawArray = new int[count * 2];
		for (int index = 0; index < count; index++)
		{
			Coordinate c = cvec.get(index);
			rawArray[2 * index] = c.x;
			rawArray[2 * index + 1] = c.y;
		}
		return rawArray;
	}

	/**
	 * Given a ArrayList of coordinates, we need to flatten them into an array
	 * of ints before we can stuff them into a map for flattening and storage.
	 * 
	 * @param cvec
	 *            : a ArrayList of Coordinate objects
	 * @return : a simple array containing the x/y values of the coordinates as
	 *         [x1,y1,x2,y2,x3,y3...]
	 */
	private int[] intArrayListToArray(ArrayList<Integer> cvec)
	{
		int count = cvec.size();
		int[] rawArray = new int[count];
		for (int index = 0; index < count; index++)
		{
			Integer c = cvec.get(index);
			rawArray[index] = c;
		}
		return rawArray;
	}

	/**
	 * Save game state so that the user does not lose anything if the game
	 * process is killed while we are in the background.
	 * 
	 * @return a Bundle with this view's state
	 */
	public Bundle saveState()
	{
		Bundle map = new Bundle();

		map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));
		map.putInt("mDirection", Integer.valueOf(mDirection));
		map.putIntArray("mCommandList", intArrayListToArray(mCommandList));
		map.putLong("mMoveDelay", Long.valueOf(mMoveDelay));
		map.putLong("mScore", Long.valueOf(mScore));
		map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));

		return map;
	}
	
	public void setOrientation(int orientation)
	{
		this.orientation=orientation;		
	}

	/**
	 * Given a flattened array of ordinate pairs, we reconstitute them into a
	 * ArrayList of Coordinate objects
	 * 
	 * @param rawArray
	 *            : [x1,y1,x2,y2,...]
	 * @return a ArrayList of Coordinates
	 */
	private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray)
	{
		ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();

		int coordCount = rawArray.length;
		for (int index = 0; index < coordCount; index += 2)
		{
			Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);
			coordArrayList.add(c);
		}
		return coordArrayList;
	}

	/**
	 * Given a flattened array of ordinate pairs, we reconstitute them into a
	 * ArrayList of Coordinate objects
	 * 
	 * @param rawArray
	 *            : [x1,y1,x2,y2,...]
	 * @return a ArrayList of Coordinates
	 */
	private ArrayList<Integer> intArrayToArrayList(int[] rawArray)
	{
		ArrayList<Integer> coordArrayList = new ArrayList<Integer>();

		int coordCount = rawArray.length;
		for (int index = 0; index < coordCount; index++)
		{
			Integer c = new Integer(rawArray[index]);
			coordArrayList.add(c);
		}
		return coordArrayList;
	}

	/**
	 * Restore game state if our process is being relaunched
	 * 
	 * @param icicle
	 *            a Bundle containing the game state
	 */
	public void restoreState(Bundle icicle)
	{
		setMode(PAUSE);

		mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));
		mDirection = icicle.getInt("mDirection");
		mCommandList = intArrayToArrayList(icicle.getIntArray("mCommandList"));
		mMoveDelay = icicle.getLong("mMoveDelay");
		mScore = icicle.getLong("mScore");
		mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));
	}
	
	/*
	 * handles key events in the game. Update the direction our snake is
	 * traveling based on the DPAD. Ignore events that would cause the snake to
	 * immediately turn back on itself.
	 * 
	 * (non-Javadoc)
	 * 
	 * @see android.view.View#onKeyDown(int, android.os.KeyEvent)
	 */
	
	/** Show an event in the LogCat view, for debugging */
	private void dumpEvent(MotionEvent event) {
	   String names[] = { "DOWN" , "UP" , "MOVE" , "CANCEL" , "OUTSIDE" ,
	      "POINTER_DOWN" , "POINTER_UP" , "7?" , "8?" , "9?" };
	   StringBuilder sb = new StringBuilder();
	   int action = event.getAction();
	   int actionCode = action & MotionEvent.ACTION_MASK;
	   sb.append("event ACTION_" ).append(names[actionCode]);
	   if (actionCode == MotionEvent.ACTION_POINTER_DOWN
	         || actionCode == MotionEvent.ACTION_POINTER_UP) {
	      sb.append("(pid " ).append(
	      action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
	      sb.append(")" );
	   }
	   sb.append("[" );
	   for (int i = 0; i < event.getPointerCount(); i++) {
	      sb.append("#" ).append(i);
	      sb.append("(pid " ).append(event.getPointerId(i));
	      sb.append(")=" ).append((int) event.getX(i));
	      sb.append("," ).append((int) event.getY(i));
	      if (i + 1 < event.getPointerCount())
	         sb.append(";" );
	   }
	   sb.append("]" );
	   Log.d(TAG, sb.toString());
	}	

	@Override
	public boolean onTouchEvent(MotionEvent event)
	{
		Log.d(TAG,"Num "+event.getPointerCount());
		//final int action = event.getAction();

		if ((event.getAction()&MotionEvent.ACTION_MASK)==MotionEvent.ACTION_DOWN ||
			(event.getAction()&MotionEvent.ACTION_MASK)==MotionEvent.ACTION_POINTER_DOWN)
		for (int i = 0; i < event.getPointerCount(); i++)
		{
			if (event.getPointerCount()>1 && i==0) continue;
			
			int keyCode = -1;
			
			float x = event.getX(i);
			float y = event.getY(i);
			
			//Log.d(TAG, "Pressed " + x + " " + y);
			
			// Point x,y
			pointX=x;
			pointY=y;
			
			// Square crossing
	//		int w = 320;
	//		int h = 530;			
	//		float q1 = x * h / w;
	//		float q2 = (w - x) * h / w;
	//	
	//		if (y >= q1 && y >= q2)
	//			keyCode = KeyEvent.KEYCODE_DPAD_DOWN;
	//		if (y <= q1 && y <= q2)
	//			keyCode = KeyEvent.KEYCODE_DPAD_UP;
	//		if (y >= q1 && y <= q2)
	//			keyCode = KeyEvent.KEYCODE_DPAD_LEFT;
	//		if (y <= q1 && y >= q2)
	//			keyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
			
			// Virtual keyboard

			//if (x>110&&y>327&&x<210&&y<387) 
			if (x>0&&y>0&&x<320&&y<50) 
			{	
				keyCode = KeyEvent.KEYCODE_DPAD_UP;
				keyPressed=keyCode;
			}
			//if (x>110&&y>443&&x<210&&y<503) 
			if (x>0&&y>480&&x<320&&y<533)
			{
				keyCode = KeyEvent.KEYCODE_DPAD_DOWN;
				keyPressed=keyCode;
			}
			//if (x>5&&y>385&&x<105&&y<445)
			if (x>0&&y>50&&x<160&&y<480)
			{
				keyCode = KeyEvent.KEYCODE_DPAD_LEFT;
				keyPressed=keyCode;
			}
			//if (x>215&&y>385&&x<315&&y<445)
			if (x>160&&y>50&&x<320&&y<480)
			{
				keyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
				keyPressed=keyCode;
			}
			
			if (keyCode!=-1)
			{
				//inst.sendKeyDownUpSync( keyCode );
				KeyEvent eventDown = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
				KeyEvent eventUp = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
				dispatchKeyEvent(eventDown);
				dispatchKeyEvent(eventUp);	
				
				if (vibration)
				{
					vib.vibrate(PRESSKEY_VIB_DURATION);
				}				
			}
			
			invalidate();
		}
		
		return true; //super.onTouchEvent(event);
	}
    /*
     * handles key events in the game. Update the direction our snake is traveling
     * based on the DPAD. Ignore events that would cause the snake to immediately
     * turn back on itself.
     * 
     * (non-Javadoc)
     * 
     * @see android.view.View#onKeyDown(int, android.os.KeyEvent)
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent msg) 
    {
    	Log.d(TAG, "Keypressed "+keyCode);
    	
		if (keyCode == KeyEvent.KEYCODE_DPAD_UP)
		{
			if (mMode == READY | mMode == WIN | mMode == LOSE)
			{
				/*
				 * At the beginning of the game, or the end of a previous one,
				 * we should start a new game.
				 */
				initNewGame();
				setMode(RUNNING);
				update();
				return (true);
			}

			if (mMode == PAUSE)
			{
				/*
				 * If the game is merely paused, we should just continue where
				 * we left off.
				 */
				setMode(RUNNING);
				update();
				return (true);
			}
		}
		
		if (key2interacton)
		{
			if (keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
			{
				keyCode=0;				
			}
			else
			{
				if (mDirection == SOUTH)
				{
					if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) 
						keyCode=KeyEvent.KEYCODE_DPAD_RIGHT;
					else
					if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) 
						keyCode=KeyEvent.KEYCODE_DPAD_LEFT;
				}
				
				if (mDirection == EAST)
				{
					if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) 
						keyCode=KeyEvent.KEYCODE_DPAD_UP;
					else
					if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) 
						keyCode=KeyEvent.KEYCODE_DPAD_DOWN;
				}
				
				if (mDirection == WEST)
				{
					if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) 
						keyCode=KeyEvent.KEYCODE_DPAD_DOWN;
					else
					if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) 
						keyCode=KeyEvent.KEYCODE_DPAD_UP;
				}				
			}
		}

		if (keyCode == KeyEvent.KEYCODE_DPAD_UP)
		{
			if (mCommandList.isEmpty())
			{
				if (mDirection != SOUTH)
				{
					mCommandList.add(NORTH);
				}
			} else
			{
				if (mCommandList.get(mCommandList.size() - 1) != NORTH
						|| dupCommand == true)
				{
					mCommandList.add(NORTH);
				}
			}
			return (true);
		}

		if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
		{
			if (mCommandList.isEmpty())
			{
				if (mDirection != NORTH)
				{
					mCommandList.add(SOUTH);
				}
			} else
			{
				if (mCommandList.get(mCommandList.size() - 1) != SOUTH
						|| dupCommand == true)
				{
					mCommandList.add(SOUTH);
				}
			}
			return (true);
		}

		if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT)
		{
			if (mCommandList.isEmpty())
			{
				if (mDirection != EAST)
				{
					mCommandList.add(WEST);
				}
			} else
			{
				if (mCommandList.get(mCommandList.size() - 1) != WEST
						|| dupCommand == true)
				{
					mCommandList.add(WEST);
				}
			}
			return (true);
		}

		if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT)
		{
			if (mCommandList.isEmpty())
			{
				if (mDirection != WEST)
				{
					mCommandList.add(EAST);
				}
			} else
			{
				if (mCommandList.get(mCommandList.size() - 1) != EAST
						|| dupCommand == true)
				{
					mCommandList.add(EAST);
				}
			}
			return (true);
		}

		return super.onKeyDown(keyCode, msg);
	}

	/**
	 * Sets the TextView that will be used to give information (such as "Game
	 * Over" to the user.
	 * 
	 * @param newView
	 */
	public void setTextView(TextView newView)
	{
		mStatusText = newView;
	}

	/**
	 * Updates the current mode of the application (RUNNING or PAUSED or the
	 * like) as well as sets the visibility of textview for notification
	 * 
	 * @param newMode
	 */
	public void setMode(int newMode)
	{
		int oldMode = mMode;
		mMode = newMode;

		if (newMode == RUNNING & oldMode != RUNNING)
		{
			mStatusText.setVisibility(View.INVISIBLE);
			update();
			return;
		}

		Resources res = getContext().getResources();
		CharSequence str = "";
		if (newMode == PAUSE)
		{
			str = res.getText(R.string.mode_pause);
		}
		if (newMode == READY)
		{
			//20130122 (Ale):I commented the string related to the mode_ready
			//Now, as soon as you start the game (1st time), the level should be
			//displayed, without the string.
			/*str = res.getText(R.string.mode_ready);*/
			mStatusText.setVisibility(View.INVISIBLE);
			//Set il mode to RUNNING and initialize a new game
			Log.d("ViperView", "Entra in mode READY");
			initNewGame();
			setMode(RUNNING);
			//initViperView();
			update();
			return;
		}
		if (newMode == LOSE)
		{
			str = res.getString(R.string.mode_lose_prefix) + mScore
					+ res.getString(R.string.mode_lose_suffix);
		}
		if (newMode == WIN)
		{
			currentLevel++;
			str = res.getString(R.string.mode_win_prefix) + mScore
					+ res.getString(R.string.mode_win_suffix);
			Config.Save();
		}		

		mStatusText.setText(str);
		mStatusText.setVisibility(View.VISIBLE);
	}

	/**
	 * Selects a location within the garden that is not currently covered by the
	 * snake. Currently _could_ go into an infinite loop if the snake currently
	 * fills the garden, but we'll leave discovery of this prize to a truly
	 * excellent snake-player.
	 * 
	 */
	private void addApple(int x, int y)
	{
		Coordinate newCoord = null;
		boolean found = false;
		while (!found)
		{
			// Choose a new location for our apple
			int newX = x;
			int newY = y;
			newCoord = new Coordinate(newX, newY);

			// Make sure it's not already under the snake
			boolean collision = false;
			int snakelength = mSnakeTrail.size();
			for (int index = 0; index < snakelength; index++)
			{
				if (mSnakeTrail.get(index).equals(newCoord))
				{
					collision = true;
				}
			}
			// if we're here and there's been no collision, then we have
			// a good location for an apple. Otherwise, we'll circle back
			// and try again
			found = !collision;
		}
		if (newCoord == null)
		{
			Log.e(TAG, "Somehow ended up with a null newCoord!");
		}
		mAppleList.add(newCoord);
	}

	/**
	 * Selects a random location within the garden that is not currently covered
	 * by the snake. Currently _could_ go into an infinite loop if the snake
	 * currently fills the garden, but we'll leave discovery of this prize to a
	 * truly excellent snake-player.
	 * 
	 */
	/*
	 * private void addRandomApple() { Coordinate newCoord = null; boolean found
	 * = false; while (!found) { // Choose a new location for our apple int newX
	 * = 1 + RNG.nextInt(mXTileCount - 2); int newY = 1 +
	 * RNG.nextInt(mYTileCount - 2); newCoord = new Coordinate(newX, newY);
	 * 
	 * // Make sure it's not already under the snake boolean collision = false;
	 * int snakelength = mSnakeTrail.size(); for (int index = 0; index <
	 * snakelength; index++) { if (mSnakeTrail.get(index).equals(newCoord)) {
	 * collision = true; } } // if we're here and there's been no collision,
	 * then we have // a good location for an apple. Otherwise, we'll circle
	 * back // and try again found = !collision; } if (newCoord == null) {
	 * Log.e(TAG, "Somehow ended up with a null newCoord!"); }
	 * mAppleList.add(newCoord); }
	 */

	/**
	 * Handles the basic update loop, checking to see if we are in the running
	 * state, determining if a move should be made, updating the snake's
	 * location.
	 */
	public void update()
	{
		if (mMode == RUNNING)
		{
			long now = System.currentTimeMillis();
			
			if (now - mLastMove > mMoveDelay)
			{
				//clearTiles();
				//updateWalls();
				//updateApples();		
				
				updateSnake();
				invalidate();
				mLastMove = now;
			}
			
			//invalidate();
			
			mRedrawHandler.sleep(mMoveDelay/2);
		}

	}

	/**
	 * Draws some walls.
	 * 
	 */
	private void updateWalls()
	{
		int tile;

		for (int x = 0; x < mXTileCount; x++)
		{
			for (int y = 0; y < mYTileCount; y++)
			{
				tile = levelBuffer[(y + x * mXTileCount) * 2];
				if (WALL_TILE + tile != EGG)
				{
					setTile(WALL_TILE + tile, x, y);
				}
			}
		}
	}

	/**
	 * Draws some apples.
	 * 
	 */
	private void updateApples()
	{
		for (Coordinate c : mAppleList)
		{
			setTile(EGG, c.x, c.y);
		}
	}

	/**
	 * Figure out which way the snake is going, see if he's run into anything
	 * (the walls, himself, or an apple). If he's not going to die, we then add
	 * to the front and subtract from the rear in order to simulate motion. If
	 * we want to grow him, we don't subtract from the rear.
	 * 
	 */
	private void updateSnake()
	{
		boolean curDir = true;
		boolean growSnake = false;

		// grab the snake by the head
		Coordinate head = mSnakeTrail.get(0);
		Coordinate newHead = new Coordinate(1, 1);

		// Look for boxing killing condition

		int upt = getTile(head.x, head.y - 1);
		int dwt = getTile(head.x, head.y + 1);
		int lft = getTile(head.x - 1, head.y);
		int rgt = getTile(head.x + 1, head.y);

		//Log.d(TAG, "box " + upt + " " + dwt + " " + lft + " " + rgt + " ");

		if (!(upt == WALL_TILE || upt == EGG)
				&& !(dwt == WALL_TILE || dwt == EGG)
				&& !(lft == WALL_TILE || lft == EGG)
				&& !(rgt == WALL_TILE || rgt == EGG))
		{
			Log.d(TAG, "Boxing death");
			if (vibration)
			{
				vib.vibrate(SELFBITE_VIB_DURATION);
			}
			setMode(LOSE);
			return;
		}

		// String s="";
		// for(int i=0; i<mCommandList.size();i++)
		// s=s+" "+mCommandList.get(i);
		// if (!mCommandList.isEmpty())
		// Log.d(TAG,"Buffer: "+s);

		// New direction
		if (!mCommandList.isEmpty())
		{
			switch (mCommandList.get(0))
			{
			case EAST:
			{
				newHead = new Coordinate(head.x + 1, head.y);
				break;
			}
			case WEST:
			{
				newHead = new Coordinate(head.x - 1, head.y);
				break;
			}
			case NORTH:
			{
				newHead = new Coordinate(head.x, head.y - 1);
				break;
			}
			case SOUTH:
			{
				newHead = new Coordinate(head.x, head.y + 1);
				break;
			}
			}

			// Collision detection wall
			if (!(getTile(newHead.x, newHead.y) >= WALL_TILE + 1 && getTile(
					newHead.x, newHead.y) <= EGG - 1)
					&& !mSnakeTrail.get(1).equals(newHead))
			{
				curDir = false;
				mDirection = mCommandList.get(0);
				mCommandList.remove(0);
			}
		}

		if (curDir)
		{
			switch (mDirection)
			{
			case EAST:
			{
				newHead = new Coordinate(head.x + 1, head.y);
				break;
			}
			case WEST:
			{
				newHead = new Coordinate(head.x - 1, head.y);
				break;
			}
			case NORTH:
			{
				newHead = new Coordinate(head.x, head.y - 1);
				break;
			}
			case SOUTH:
			{
				newHead = new Coordinate(head.x, head.y + 1);
				break;
			}
			}
		}

		// Look for collisions with itself
		int snakelength = mSnakeTrail.size();
		for (int snakeindex = 0; snakeindex < snakelength; snakeindex++)
		{
			Coordinate c = mSnakeTrail.get(snakeindex);
			if (c.equals(newHead))
			{
				Log.d(TAG, "Self-bite death "+head.x+" "+head.y+ " "+newHead.x+" "+newHead.y);
				if (vibration)
				{
					vib.vibrate(SELFBITE_VIB_DURATION);
				}
				setMode(LOSE);
				return;
			}
		}

		// Collision detection wall
		if (getTile(newHead.x, newHead.y) >= WALL_TILE + 1
				&& getTile(newHead.x, newHead.y) <= EGG - 1)
		{
			newHead = head;

			if (!mCommandList.isEmpty())
			{
				mCommandList.remove(0);
			}
		}
		// For now we have a 1-square wall around the entire arena
		// if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount -
		// 2)
		// || (newHead.y > mYTileCount - 2))
		// {
		// setMode(LOSE);
		// return;

		// }

		// Look for apples
		int applecount = mAppleList.size();
		for (int appleindex = 0; appleindex < applecount; appleindex++)
		{
			Coordinate c = mAppleList.get(appleindex);
			if (c.equals(newHead))
			{
				mAppleList.remove(c);
				if (vibration)
				{
					vib.vibrate(EGG_VIB_DURATION);
				}
				// addRandomApple();

				mScore++;
				// mMoveDelay *= 0.9;

				growSnake = true;
				
				if(mAppleList.isEmpty())
				{
					if (vibration)
					{
						long pattern[] = new long[4];
						
						pattern[0]=0;
						pattern[1]=WIN_VIB_DURATION*2/5;
						pattern[2]=WIN_VIB_DURATION*1/5;
						pattern[3]=WIN_VIB_DURATION*2/5;

						vib.vibrate(pattern, -1);
					}
					setMode(WIN);
				}

				break;
			}
		}

		// push a new head onto the ArrayList and pull off the tail
		if (newHead != head)
		{
			mSnakeTrail.add(0, newHead);
			// except if we want the snake to grow
			if (!growSnake)
			{
				setTile(WALL_TILE, mSnakeTrail.get(mSnakeTrail.size() - 1).x,
						mSnakeTrail.get(mSnakeTrail.size() - 1).y);
				mSnakeTrail.remove(mSnakeTrail.size() - 1);
			}
		}

		Coordinate c;
		int index = 0;
		int rot = 0, dx = 0, dy = 0, dc = 0;
		// for (Coordinate c : mSnakeTrail)
		for (index = 0; index <= 2; index++)
		{
			if(index==2) index=mSnakeTrail.size() - 1;
			c = mSnakeTrail.get(index);

			if (index == 0)
			{
				dx = c.x - mSnakeTrail.get(1).x;
				dy = c.y - mSnakeTrail.get(1).y;

				if (dx == +1)
					rot = 0;
				if (dy == -1)
					rot = 1;
				if (dx == -1)
					rot = 2;
				if (dy == +1)
					rot = 3;

				setTile(SNAKE_TILE + rot, c.x, c.y);
			} else
			{
				if (index < mSnakeTrail.size() - 1)
				{
					dx = mSnakeTrail.get(index - 1).x
							- mSnakeTrail.get(index + 1).x;
					dy = mSnakeTrail.get(index - 1).y
							- mSnakeTrail.get(index + 1).y;
					dc = Math.abs(mSnakeTrail.get(index - 1).x
							- mSnakeTrail.get(index).x);

					if (dx == +2 || dx == -2)
						rot = 0;
					if (dy == +2 || dy == -2)
						rot = 1;

					if (dx == +1 && dy == +1 && dc == 0)
						rot = 5;
					if (dx == +1 && dy == -1 && dc == 0)
						rot = 2;
					if (dx == -1 && dy == +1 && dc == 0)
						rot = 4;
					if (dx == -1 && dy == -1 && dc == 0)
						rot = 3;

					if (dx == +1 && dy == +1 && dc == 1)
						rot = 3;
					if (dx == +1 && dy == -1 && dc == 1)
						rot = 4;
					if (dx == -1 && dy == +1 && dc == 1)
						rot = 2;
					if (dx == -1 && dy == -1 && dc == 1)
						rot = 5;

					// if(rot>1)
					// Log.d(TAG,"Curve "+dx+" "+dy+" "+dc+" "+rot);

					setTile(SNAKE_TILE + 4 + rot, c.x, c.y);
				} else
				{
					dx = c.x - mSnakeTrail.get(mSnakeTrail.size() - 2).x;
					dy = c.y - mSnakeTrail.get(mSnakeTrail.size() - 2).y;

					if (dx == -1)
						rot = 0;
					if (dy == +1)
						rot = 1;
					if (dx == +1)
						rot = 2;
					if (dy == -1)
						rot = 3;

					setTile(SNAKE_TILE + 10 + rot * 2 + (flagQueue ? 0 : 1),
							c.x, c.y);
					flagQueue = !flagQueue;
				}
			}
			//index++;
		}
	}

	/**
	 * Simple class containing two integer values and a comparison function.
	 * There's probably something I should use instead, but this was quick and
	 * easy to build.
	 * 
	 */
	private class Coordinate
	{
		public int x;
		public int y;

		public Coordinate(int newX, int newY)
		{
			x = newX;
			y = newY;
		}

		public boolean equals(Coordinate other)
		{
			if (x == other.x && y == other.y)
			{
				return true;
			}
			return false;
		}

		@Override
		public String toString()
		{
			return "Coordinate: [" + x + "," + y + "]";
		}
	}

}
